using System;
using System.Threading.Tasks;
using System.Windows.Input;

namespace Phoenix.Commands
{
    public class AsyncDelegateCommand : AsyncDelegateCommand<object>
    {
        public AsyncDelegateCommand(Func<Task> action) : base(o=>action())
        {
        }

        public AsyncDelegateCommand(Func<Task> action, Func<bool> canExecute) : base(o=>action(), o=>canExecute())
        {
        }
    }

    public class AsyncDelegateCommand<T> : ICommand
    {
        readonly Func<T, Task> action;
        readonly Func<T, bool> canExecute;
        bool currentlyExecuting;

        public AsyncDelegateCommand(Func<T, Task> action, Func<T, bool> canExecute = null)
        {
            this.action = action;
            this.canExecute = canExecute;
        }

        public void Execute(object parameter)
        {
            if (currentlyExecuting)
                return;
            currentlyExecuting = true;
            RaiseCanExecuteChanged();

            var task = action((T) parameter);
            if (task == null)
            {
                currentlyExecuting = false;
                RaiseCanExecuteChanged();
                return;
            }

            task
                .ContinueWith(t =>
                {
                    currentlyExecuting = false;
                    RaiseCanExecuteChanged();

                    if (t.IsFaulted)
                        OnErrorOccured(new UnhandledExceptionEventArgs(t.Exception, false));
                }, TaskScheduler.FromCurrentSynchronizationContext());
        }

        public bool CanExecute(object parameter)
        {
            if (currentlyExecuting)
                return false;

            return canExecute == null || canExecute((T)parameter);
        }

        /// <summary>
        /// Raised when an error occurs with the asynchronous operation
        /// </summary>
        public event UnhandledExceptionEventHandler ErrorOccured;

        public void OnErrorOccured(UnhandledExceptionEventArgs e)
        {
            var handler = ErrorOccured;
            if (handler != null) handler(this, e);
        }

        public event EventHandler CanExecuteChanged;

        public void RaiseCanExecuteChanged()
        {
            var handler = CanExecuteChanged;
            if (handler != null) handler(this, EventArgs.Empty);
        }
    }
}